Processing Multiple Exceptions

In its simplest form, a try block has a single catch block. In reality, though, you often run into situations where the statements within a try block could trigger numerous possible exceptions. Create a new C# Console Application project named ProcessMultipleExceptions, add the Car.cs, Radio.cs, and CarIsDeadException.cs files from the previous CustomException example into the new project (via Project ? Add Existing Item), and update your namespace names accordingly.

Now, update the Car’s Accelerate() method to also throw a predefined base class library ArgumentOutOfRangeException if you pass an invalid parameter (which we will assume is any value less than zero). Note the constructor of this exception class takes the name of the offending argument as the first string, followed by a message describing the error.

// Test for invalid argument before proceeding.
public void Accelerate(int delta)
{
    if(delta < 0)
        throw new
            ArgumentOutOfRangeException("delta", "Speed must be greater than zero!");
    ...
}

The catch logic could now specifically respond to each type of exception:

static void Main(string[] args)
{
    Console.WriteLine("***** Handling Multiple Exceptions *****\n");
    Car myCar = new Car("Rusty", 90);
    try
    {
        // Trip Arg out of range exception.
        myCar.Accelerate(-10);
    }
    catch (CarIsDeadException e)
    {
        Console.WriteLine(e.Message);
    }
    catch (ArgumentOutOfRangeException e)
    {
        Console.WriteLine(e.Message);
    }
    Console.ReadLine();
}

When you are authoring multiple catch blocks, you must be aware that when an exception is thrown, it will be processed by the “first available” catch. To illustrate exactly what the “first available” catch means, assume you retrofitted the previous logic with an additional catch scope that attempts to handle all exceptions beyond CarIsDeadException and ArgumentOutOfRangeException by catching a general System.Exception as follows:

// This code will not compile!
static void Main(string[] args)
{
    Console.WriteLine("***** Handling Multiple Exceptions *****\n");
    Car myCar = new Car("Rusty", 90);

    try
    {
        // Trigger an argument out of range exception.
        myCar.Accelerate(-10);
    }
    catch(Exception e)
    {
        // Process all other exceptions?
        Console.WriteLine(e.Message);
    }
    catch (CarIsDeadException e)
    {
        Console.WriteLine(e.Message);
    }
    catch (ArgumentOutOfRangeException e)
    {
        Console.WriteLine(e.Message);
    }
    Console.ReadLine();
}

This exception-handling logic generates compile-time errors. The problem is due to the fact that the first catch block can handle anything derived from System.Exception (given the “is-a” relationship), including the CarIsDeadException and ArgumentOutOfRangeException types. Therefore, the final two catch blocks are unreachable!

The rule of thumb to keep in mind is to make sure your catch blocks are structured such that the very first catch is the most specific exception (i.e., the most derived type in an exception-type inheritance chain), leaving the final catch for the most general (i.e., the base class of a given exception inheritance chain, in this case System.Exception).

Thus, if you wish to define a catch block that will handle any errors beyond CarIsDeadException and ArgumentOutOfRangeException, you could write the following:

// This code compiles just fine.
static void Main(string[] args)
{
    Console.WriteLine("***** Handling Multiple Exceptions *****\n");
    Car myCar = new Car("Rusty", 90);
    try
    {
        // Trigger an argument out of range exception.
        myCar.Accelerate(-10);
    }
    catch (CarIsDeadException e)
    {
        Console.WriteLine(e.Message);
    }
    catch (ArgumentOutOfRangeException e)
    {
        Console.WriteLine(e.Message);
    }
    // This will catch any other exception
    // beyond CarIsDeadException or
    // ArgumentOutOfRangeException.
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    Console.ReadLine();
}

Note Where at all possible, always favor catching specific exception classes, rather than a general System.Exception. Though it might appear to make life simple in the short term (you may think, “Ah! This catches all the other things I don’t care about.”), in the long term you could end up with strange runtime crashes, as a more serious error was not directly dealt with in your code. Remember, a final catch block that deals with System.Exception tends to be very general indeed.

General catch Statements

C# also supports a “general” catch scope that does not explicitly receive the exception object thrown by a given member:

// A generic catch.
static void Main(string[] args)
{
    Console.WriteLine("***** Handling Multiple Exceptions *****\n");
    Car myCar = new Car("Rusty", 90);
    try
    {
        myCar.Accelerate(90);
    }
    catch
    {
        Console.WriteLine("Something bad happened...");
    }
    Console.ReadLine();
}

Obviously, this is not the most informative way to handle exceptions, since you have no way to obtain meaningful data about the error that occurred (such as the method name, call stack, or custom message). Nevertheless, C# does allow for such a construct, which can be helpful when you want to handle all errors in a very, very general fashion.

Rethrowing Exceptions

When you catch an exception, it is permissible for the logic in a try block to rethrow the exception up the call stack to the previous caller. To do so, simply use the throw keyword within a catch block. This passes the exception up the chain of calling logic, which can be helpful if your catch block is only able to partially handle the error at hand:

// Passing the buck.
static void Main(string[] args)
{
...
    try
    {
        // Speed up car logic...
    }
    catch(CarIsDeadException e)
    {
        // Do any partial processing of this error and pass the buck.
        throw;
    }
...
}

Be aware that in this example code, the ultimate receiver of CarIsDeadException is the CLR, since it is the Main() method rethrowing the exception. Because of this, your end user is presented with a system-supplied error dialog box. Typically, you would only rethrow a partial handled exception to a caller that has the ability to handle the incoming exception more gracefully.

Notice as well that we are not explicitly rethrowing the CarIsDeadException object, but rather making use of the throw keyword with no argument. We’re not creating a new exception object; we’re just rethrowing the original exception object (with all its original information). Doing so preserves the context of the original target.

Inner Exceptions

As you may suspect, it is entirely possible to trigger an exception at the time you are handling another exception. For example, assume you are handling a CarIsDeadException within a particular catch scope, and during the process you attempt to record the stack trace to a file on your C: drive named carErrors.txt (you must specify you are using the System.IO namespace to gain access to these I/Ocentric types):

catch(CarIsDeadException e)
{
    // Attempt to open a file named carErrors.txt on the C drive.
    FileStream fs = File.Open(@"C:\carErrors.txt", FileMode.Open);
    ...
}

Now, if the specified file is not located on your C: drive, the call to File.Open() results in a FileNotFoundException! Later in this text, you will learn all about the System.IO namespace where you’ll discover how to programmatically determine whether a file exists on the hard drive before attempting to open the file in the first place (thereby avoiding the exception altogether). However, to stay focused on the topic of exceptions, assume the exception has been raised.

When you encounter an exception while processing another exception, best practice states that you should record the new exception object as an “inner exception” within a new object of the same type as the initial exception. (That was a mouthful!) The reason you need to allocate a new object of the exception being handled is that the only way to document an inner exception is via a constructor parameter. Consider the following code:

catch (CarIsDeadException e)
{
    try
    {
        FileStream fs = File.Open(@"C:\carErrors.txt", FileMode.Open);
        ...
    }
    catch (Exception e2)
    {
        // Throw an exception that records the new exception,
        // as well as the message of the first exception.
        throw new CarIsDeadException(e.Message, e2);
    }
}

Notice in this case, we have passed in the FileNotFoundException object as the second parameter to the CarIsDeadException constructor. Once we have configured this new object, we throw it up the call stack to the next caller, which in this case would be the Main() method.

Given that there is no “next caller” after Main() to catch the exception, we would be again presented with an error dialog box. Much like the act of rethrowing an exception, recording inner exceptions is usually only useful when the caller has the ability to gracefully catch the exception in the first place. If this is the case, the caller’s catch logic can make use of the InnerException property to extract the details of the inner exception object.

The Finally Block

A try/catch scope may also define an optional finally block. The purpose of a finally block is to ensure that a set of code statements will always execute, exception (of any type) or not. To illustrate, assume you wish to always power down the car’s radio before exiting Main(), regardless of any handled exception:

static void Main(string[] args)
{
    Console.WriteLine("***** Handling Multiple Exceptions *****\n");
    Car myCar = new Car("Rusty", 90);
    myCar.CrankTunes(true);

    try
    {
        // Speed up car logic.
    }
    catch(CarIsDeadException e)
    {
        // Process CarIsDeadException.
    }
    catch(ArgumentOutOfRangeException e)
    {
        // Process ArgumentOutOfRangeException.
    }
    catch(Exception e)
    {
        // Process any other Exception.
    }
    finally
    {
        // This will always occur. Exception or not.
        myCar.CrankTunes(false);
    }
    Console.ReadLine();
}

If you did not include a finally block, the radio would not be turned off if an exception is encountered (which may or may not be problematic). In a more real-world scenario, when you need to dispose of objects, close a file, detach from a database (or whatever), a finally block ensures a location for proper cleanup.